import json
import logging
import sys
from dataclasses import asdict
from datetime import datetime

from PIL import Image
from PySide6.QtCore import QObject, Qt, QThread, QTimer, Signal, Slot
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QDialog,
    QFileDialog,
    QMainWindow,
    QMenu,
    QMessageBox,
    QProgressDialog,
    QToolButton,
)

from src.models.filter_proxy_model import DirectSortProxyModel
from src.models.student import Student
from src.models.student_table import (
    HEADERS_INDEX,
    ButtonDelegate,
    StatusDelegate,
    StudentTableModel,
)
from src.ui import (
    add_student_dialog_ui_inst,
    bartender_dialog_ui_inst,
    main_ui_inst,
    printer_dialog_ui_inst,
    selected_dialog_ui_inst,
)
from src.utils.config import APPConfig, LayoutConfig, PrinterConfig
from src.utils.log import setup_logger
from src.utils.merge_table_data import merge_table_files
from src.utils.resources_path import resource_path
from src.utils.tspl import do_print, generate_bmp
from src.utils.win32_printer_config import (
    get_printer_dpi,
    get_printer_paper_size,
    list_printers,
    open_printer_properties,
)

setup_logger()

logger = logging.getLogger("src.main")

CONFIG_PATH = "config.json"
DEBOUNCE_TIME_MS = 500
ICON = resource_path("src/assets/hit.ico")


class FilterWorker(QObject):
    finished = Signal(list)  # 发送过滤后的行索引列表

    def __init__(self, source_model, filter_text, name_col, id_col):
        super().__init__()
        self.source_model = source_model
        self.filter_text = filter_text.lower()
        self.name_col = name_col
        self.id_col = id_col
        self._is_interrupted = False
        self._is_running = False

    def interrupt(self):
        self._is_interrupted = True

    def is_running(self):
        return self._is_running

    def run(self):
        # 在后台线程执行过滤逻辑
        self._is_running = True
        logger.debug("后台线程开始过滤: %s", self.filter_text)
        filtered_rows = []
        for row in range(self.source_model.rowCount()):
            # 在这里检查是否接收到quit()信号
            if self._is_interrupted:
                logger.debug("后台线程收到中断请求")
                self._is_running = False

                break

            name = self.source_model.index(row, self.name_col).data().lower()
            student_id = self.source_model.index(row, self.id_col).data().lower()
            if not isinstance(name, str):
                name = str(name)

            if not isinstance(student_id, str):
                student_id = str(student_id)
            if self.filter_text in name or self.filter_text in student_id:
                filtered_rows.append(row)
        self.finished.emit(filtered_rows)
        self._is_running = False

        logger.debug("后台线程结束过滤")


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # 创建 UI 实例
        self.ui = main_ui_inst
        self.return_pressed_flag = False
        self.worker_thread = QThread()
        # 设置 UI
        self.ui.setupUi(self)
        self.setup_config()

        # self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
        self.setup()

    def setup_config(self):
        try:
            with open(CONFIG_PATH) as f:
                config_dict = json.load(f)
                self.config = APPConfig.from_dict(config_dict)

        except FileNotFoundError:
            self.config = APPConfig()
            with open(CONFIG_PATH, "w") as f:
                json.dump(asdict(self.config), f)
        except Exception:
            QMessageBox.warning(self, "警告", "配置文件损坏，使用默认配置")
            self.config = APPConfig()
            with open(CONFIG_PATH, "w") as f:
                json.dump(asdict(self.config), f)
        logger.info(f"使用配置: {self.config}")

    def show_help_recording_mode(self):
        help_text = """
        <p>关于录入模式：</p>
        <ol>
            <li>点击“开始录制”按钮后，会将焦点强制锁在输入框内，无法修改其他数据，此时可以使用扫码枪快速扫描条码录入信息。</li>
            <li>点击“结束录制”按钮后，会将焦点解锁，此时可以自由修改表格中的数据。</li>
        </ol>
        """
        msg_box = QMessageBox(self, icon=QMessageBox.Icon.Information)
        msg_box.setWindowTitle("帮助")
        msg_box.setTextFormat(Qt.TextFormat.RichText)
        msg_box.setText(help_text)
        msg_box.exec()

    def show_help_print(self):
        help_text = """
        <p>打印可以选择两种模式：</p>
        <ol>
            <li>通过Bartender和它的XML命令进行打印。此方法要求Bartender为Automation以上的License，版本兼容2016。
                详细可看：<a href='https://help.seagullscientific.com/2016/en/#../Subsystems/BTXML/Content/GettingStarted_BTXMLScript.htm'>Bartender帮助文档</a></li>
            <li>通过Python调用win32print模块进行打印。此方法要求打印机支持TSPL命令。系统会将图片转化为TSPL命令发送到打印机中。</li>
        </ol>
        """
        msg_box = QMessageBox(self, icon=QMessageBox.Icon.Information)
        msg_box.setWindowTitle("帮助")
        msg_box.setTextFormat(Qt.TextFormat.RichText)
        msg_box.setText(help_text)
        msg_box.exec()

    def show_printer_dialog(self):
        dialog = QDialog(self)
        printers = list_printers()
        if not printers:
            QMessageBox.warning(self, "警告", "未找到打印机！")
            return
        printers = [p[2] for p in printers]
        printer_dialog_ui_inst.setupUi(dialog)
        printer_dialog_ui_inst.printer_combo_box.addItems(printers)
        if self.config.printer_config.name in printers:
            index = printer_dialog_ui_inst.printer_combo_box.findText(
                self.config.printer_config.name
            )
            printer_dialog_ui_inst.printer_combo_box.setCurrentIndex(index)
        printer_dialog_ui_inst.printer_dpi_spin_box.setValue(
            self.config.printer_config.dpi
        )
        printer_dialog_ui_inst.paper_width_spin_box.setValue(
            self.config.printer_config.paper_width_mm
        )
        printer_dialog_ui_inst.paper_height_spin_box.setValue(
            self.config.printer_config.paper_height_mm
        )
        printer_dialog_ui_inst.name_font_size_spin_box.setValue(
            self.config.layout_config.name_font_size_percent
        )
        printer_dialog_ui_inst.type_font_size_spin_box.setValue(
            self.config.layout_config.receipt_font_size_percent
        )
        printer_dialog_ui_inst.code_barcode_height_spin_box.setValue(
            self.config.layout_config.barcode_height_percent
        )
        printer_dialog_ui_inst.code_unit_width_spin_box.setValue(
            self.config.layout_config.code_moudle_width
        )
        printer_dialog_ui_inst.code_module_height_spin_box.setValue(
            self.config.layout_config.code_moudle_height_percent
        )
        printer_dialog_ui_inst.code_font_size_spin_box.setValue(
            self.config.layout_config.code_font_size_percent
        )
        printer_dialog_ui_inst.code_text_distance_spin_box.setValue(
            self.config.layout_config.code_text_distance_percent
        )
        printer_dialog_ui_inst.margin_spin_box.setValue(
            self.config.layout_config.margin_percent
        )

        def handle_click_open_printer_config_button():
            current_text = printer_dialog_ui_inst.printer_combo_box.currentText()
            if current_text:
                try:
                    open_printer_properties(current_text)
                except Exception:
                    QMessageBox.warning(
                        self,
                        "警告",
                        "此打印机无法打开配置页，请前往控制面板处理打印机配置",
                    )

        printer_dialog_ui_inst.open_printer_config_button.clicked.connect(
            handle_click_open_printer_config_button
        )

        def handle_click_printer_auto_config_button():
            current_text = printer_dialog_ui_inst.printer_combo_box.currentText()
            if current_text:
                try:
                    x_dpi, y_dpi = get_printer_dpi(current_text)
                    printer_dialog_ui_inst.printer_dpi_spin_box.setValue(
                        min(x_dpi, y_dpi)
                    )
                except Exception:
                    QMessageBox.warning(
                        self, "警告", "获取打印机dpi失败，请前往控制面板查看打印机配置"
                    )
                try:
                    pw, ph = get_printer_paper_size(current_text)
                    printer_dialog_ui_inst.paper_width_spin_box.setValue(pw)
                    printer_dialog_ui_inst.paper_height_spin_box.setValue(ph)
                except Exception:
                    QMessageBox.warning(
                        self, "警告", "获取打印纸宽高失败，请前往控制面板查看打印机配置"
                    )
                QMessageBox.information(
                    self,
                    "提示",
                    "自动配置成功！\n注意，自动配置仅供参考，具体值请以实际为准。",
                )

        printer_dialog_ui_inst.printer_auto_config_button.clicked.connect(
            handle_click_printer_auto_config_button
        )

        def handle_click_save_button():
            ph = printer_dialog_ui_inst.paper_height_spin_box.value()
            pw = printer_dialog_ui_inst.paper_width_spin_box.value()
            dpi = printer_dialog_ui_inst.printer_dpi_spin_box.value()
            name = printer_dialog_ui_inst.printer_combo_box.currentText()

            self.config.printer_config = PrinterConfig(
                is_configured=True,
                name=name,
                dpi=dpi,
                paper_height_mm=ph,
                paper_width_mm=pw,
            )
            name_font_size_percent = (
                printer_dialog_ui_inst.name_font_size_spin_box.value()
            )
            receipt_font_size_percent = (
                printer_dialog_ui_inst.type_font_size_spin_box.value()
            )
            barcode_height_percent = (
                printer_dialog_ui_inst.code_barcode_height_spin_box.value()
            )
            margin_percent = printer_dialog_ui_inst.margin_spin_box.value()
            code_moudle_width = printer_dialog_ui_inst.code_unit_width_spin_box.value()
            code_font_size_percent = (
                printer_dialog_ui_inst.code_font_size_spin_box.value()
            )
            code_text_distance_percent = (
                printer_dialog_ui_inst.code_text_distance_spin_box.value()
            )
            code_moudle_height_percent = (
                printer_dialog_ui_inst.code_module_height_spin_box.value()
            )
            self.config.layout_config = LayoutConfig(
                name_font_size_percent=name_font_size_percent,
                receipt_font_size_percent=receipt_font_size_percent,
                barcode_height_percent=barcode_height_percent,
                margin_percent=margin_percent,
                code_moudle_width=code_moudle_width,
                code_font_size_percent=code_font_size_percent,
                code_text_distance_percent=code_text_distance_percent,
                code_moudle_height_percent=code_moudle_height_percent,
            )
            with open(CONFIG_PATH, "w") as f:
                json.dump(asdict(self.config), f)
            QMessageBox.information(self, "提示", "保存成功")

        printer_dialog_ui_inst.save_button.clicked.connect(handle_click_save_button)
        printer_dialog_ui_inst.cancel_button.clicked.connect(dialog.close)

        def handle_print_preview_button():
            dpi = printer_dialog_ui_inst.printer_dpi_spin_box.value()
            ph = printer_dialog_ui_inst.paper_height_spin_box.value()
            pw = printer_dialog_ui_inst.paper_width_spin_box.value()
            try:
                img = generate_bmp(
                    name="甲乙丙丁",
                    receipt_type="学籍档案回执",
                    student_id="2023000000",
                    dpi=dpi,
                    height=int(0.9 * (ph / 25.4 * dpi)),
                    width=int(0.9 * (pw / 25.4 * dpi)),
                    name_font_size_percent=printer_dialog_ui_inst.name_font_size_spin_box.value(),
                    receipt_font_size_percent=printer_dialog_ui_inst.type_font_size_spin_box.value(),
                    barcode_height_percent=printer_dialog_ui_inst.code_barcode_height_spin_box.value(),
                    margin_percent=printer_dialog_ui_inst.margin_spin_box.value(),
                    code_moudle_width=printer_dialog_ui_inst.code_unit_width_spin_box.value(),
                    code_font_size_percent=printer_dialog_ui_inst.code_font_size_spin_box.value(),
                    code_text_distance_percent=printer_dialog_ui_inst.code_text_distance_spin_box.value(),
                    code_moudle_height_percent=printer_dialog_ui_inst.code_module_height_spin_box.value(),
                )
                img = Image.open(img)
                img.show()
            except Exception as e:
                QMessageBox.warning(self, "警告", f"打印预览失败：{e}")
                logger.error(f"打印预览失败：{e}")

        printer_dialog_ui_inst.print_preview_button.clicked.connect(
            handle_print_preview_button
        )
        dialog.exec()

    def show_bartender_dialog(self):
        dialog = QDialog(self)
        bartender_dialog_ui_inst.setupUi(dialog)
        dialog.exec()

    def show_add_one_student_dialog(self):
        dialog = QDialog(self)
        add_student_dialog_ui_inst.setupUi(dialog)

        def handle_click_add_button():
            id = add_student_dialog_ui_inst.id_line_edit.text()
            if not id:
                QMessageBox.warning(self, "警告", "请输入学号")
                return
            name = add_student_dialog_ui_inst.name_line_edit.text()
            if not name:
                QMessageBox.warning(self, "警告", "请输入姓名")
                return
            self.filter_model.add_one_row(
                Student(
                    id=id,
                    name=name,
                    is_tuanyuan_archives_received="否",
                    is_student_record_received="否",
                    last_update=datetime.now(),
                ),
                quiet=False,
            )
            dialog.close()

        add_student_dialog_ui_inst.buttonBox.accepted.connect(handle_click_add_button)
        dialog.exec()

    def handle_accepted_both_button_click(self, row):
        name = self.filter_model.index(row, HEADERS_INDEX["姓名"]).data()
        id = self.filter_model.index(row, HEADERS_INDEX["学号"]).data()

        is_tuanyuan_archives_received_index = self.filter_model.index(
            row, HEADERS_INDEX["团籍档案是否收到"]
        )
        is_tuanyuan_archives_received = is_tuanyuan_archives_received_index.data()

        is_student_record_received_index = self.filter_model.index(
            row, HEADERS_INDEX["学籍档案是否收到"]
        )
        is_student_record_received = is_student_record_received_index.data()

        if is_tuanyuan_archives_received != "是":
            self.filter_model.setData(
                is_tuanyuan_archives_received_index,
                "是",
            )
        if is_student_record_received != "是":
            self.filter_model.setData(
                is_student_record_received_index,
                "是",
            )
        logger.info(f"已确认{name}({id})的团籍档案和学籍档案已收到")
        try:
            do_print(
                name=name,
                student_id=id,
                receipt_type="学籍档案回执",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
            do_print(
                name=name,
                student_id=id,
                receipt_type="团员材料回执",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
            do_print(
                name=name,
                student_id=id,
                receipt_type="团员材料",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
        except Exception as e:
            QMessageBox.warning(self, "警告", "打印失败，请检查打印机")
            # rollback
            self.filter_model.setData(
                is_tuanyuan_archives_received_index,
                is_tuanyuan_archives_received,
            )
            self.filter_model.setData(
                is_student_record_received_index,
                is_student_record_received,
            )
            logger.error(f"打印失败，原因：{e}")

    def handle_accepted_stu_record_only_button_click(self, row):
        name = self.filter_model.index(row, HEADERS_INDEX["姓名"]).data()
        id = self.filter_model.index(row, HEADERS_INDEX["学号"]).data()

        is_student_record_received_index = self.filter_model.index(
            row, HEADERS_INDEX["学籍档案是否收到"]
        )
        is_student_record_received = is_student_record_received_index.data()

        if is_student_record_received != "是":
            self.filter_model.setData(
                is_student_record_received_index,
                "是",
            )
        logger.info(f"已确认{name}({id})的学籍档案已收到")
        try:
            do_print(
                name=name,
                student_id=id,
                receipt_type="学籍档案回执",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
        except Exception as e:
            QMessageBox.warning(self, "警告", "打印失败，请检查打印机")
            # rollback
            self.filter_model.setData(
                is_student_record_received_index,
                is_student_record_received,
            )
            logger.error(f"打印失败，原因：{e}")
        finally:
            return True

    def handle_accepted_tuanyuan_arch_only_button_click(self, row):
        name = self.filter_model.index(row, HEADERS_INDEX["姓名"]).data()
        id = self.filter_model.index(row, HEADERS_INDEX["学号"]).data()

        is_tuanyuan_archives_received_index = self.filter_model.index(
            row, HEADERS_INDEX["团籍档案是否收到"]
        )
        is_tuanyuan_archives_received = is_tuanyuan_archives_received_index.data()

        if is_tuanyuan_archives_received != "是":
            self.filter_model.setData(
                is_tuanyuan_archives_received_index,
                "是",
            )
        logger.info(
            f"已确认{name}({id})的团员材料状态为[{is_tuanyuan_archives_received_index.data()}]"
        )
        try:
            do_print(
                name=name,
                student_id=id,
                receipt_type="团员材料",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
            do_print(
                name=name,
                student_id=id,
                receipt_type="团员材料回执",
                paper_width=self.config.printer_config.paper_width_mm,
                paper_height=self.config.printer_config.paper_height_mm,
                dpi=self.config.printer_config.dpi,
                printer_name=self.config.printer_config.name,
            )
        except Exception as e:
            QMessageBox.warning(self, "警告", "打印失败，请检查打印机")
            # rollback
            self.filter_model.setData(
                is_tuanyuan_archives_received_index,
                is_tuanyuan_archives_received,
            )

            logger.error(f"打印失败，原因：{e}")

    def check_printer_config(self):
        if not self.config.printer_config.is_configured:
            msg_box = QMessageBox(self)
            msg_box.setIcon(QMessageBox.Icon.Warning)
            msg_box.setWindowTitle("警告")
            msg_box.setText("请先配置打印机！")

            # 添加自定义按钮
            config_button = msg_box.addButton(
                "立即配置", QMessageBox.ButtonRole.AcceptRole
            )
            msg_box.addButton("取消", QMessageBox.ButtonRole.RejectRole)

            msg_box.exec()

            if msg_box.clickedButton() == config_button:
                self.show_printer_dialog()
            return False
        return True

    def handle_selected_button_click(self, row):
        # 获取点击行的数据
        if not self.check_printer_config():
            return
        name = self.filter_model.index(row, HEADERS_INDEX["姓名"]).data()
        id = self.filter_model.index(row, HEADERS_INDEX["学号"]).data()
        dialog = QDialog(self)
        selected_dialog_ui_inst.setupUi(dialog)
        selected_dialog_ui_inst.name_label.setText(f"姓名: {name}")
        selected_dialog_ui_inst.id_label.setText(f"学号: {id}")
        selected_dialog_ui_inst.accepted_both_button.clicked.connect(
            lambda: (
                self.handle_accepted_both_button_click(row),
                dialog.close(),
            )
        )

        selected_dialog_ui_inst.accepted_stu_record_only_button.clicked.connect(
            lambda: (
                self.handle_accepted_stu_record_only_button_click(row),
                dialog.close(),
            )
        )

        selected_dialog_ui_inst.accepted_tuanyuan_arch_only_button.clicked.connect(
            lambda: (
                self.handle_accepted_tuanyuan_arch_only_button_click(row),
                dialog.close(),
            )
        )

        selected_dialog_ui_inst.cancel_button.clicked.connect(dialog.close)

        dialog.exec()
        # 开启录入模式后，处理完事件后清除LineEdit内容
        if self.ui.lineEdit.focus_locked:
            self.ui.lineEdit.clear()
        self.ui.tableView.sortByColumn(
            HEADERS_INDEX["最后更新时间"], Qt.SortOrder.DescendingOrder
        )

    def handle_start_recording(self):
        self.ui.lineEdit.setFocusLocked(True)

    def handle_stop_recording(self):
        self.ui.lineEdit.setFocusLocked(False)

    def get_only_one_row(self):
        row_count = self.filter_model.rowCount()
        if row_count == 1:
            self.handle_selected_button_click(0)
        else:
            QMessageBox.information(None, "提示", "未找到符合条件的同学")
            self.ui.lineEdit.clear()

    def set_table_rows_num(self):
        self.ui.table_rows_num_label.setText(
            f"当前表格总行数：{self.filter_model.rowCount()}"
        )

    def list_all_items(self):
        # 取消所有筛选条件，显示全部数据
        def filter_accepts_row(source_row, source_parent):
            return True

        self.filter_model.filterAcceptsRow = filter_accepts_row
        self.filter_model.invalidateFilter()  # 刷新筛选状态

    def filter_table_by_incompleted(self):
        # 自定义筛选逻辑
        def filter_accepts_row(source_row, source_parent):
            model = self.filter_model.sourceModel()
            # 检查学号列(0)和姓名列(1)
            is_tuanyuan_archives_received = (
                model.index(
                    source_row, HEADERS_INDEX["团籍档案是否收到"], source_parent
                )
                .data()
                .lower()
            )
            is_student_record_received = (
                model.index(
                    source_row, HEADERS_INDEX["学籍档案是否收到"], source_parent
                )
                .data()
                .lower()
            )
            return (
                is_student_record_received == "否"
                and is_tuanyuan_archives_received == "否"
            )

        self.filter_model.filterAcceptsRow = filter_accepts_row
        self.filter_model.invalidateFilter()  # 触发重新筛选

    def filter_table_by_completed(self):
        # 自定义筛选逻辑
        def filter_accepts_row(source_row, source_parent):
            model = self.filter_model.sourceModel()
            # 检查学号列(0)和姓名列(1)
            is_tuanyuan_archives_received = (
                model.index(
                    source_row, HEADERS_INDEX["团籍档案是否收到"], source_parent
                )
                .data()
                .lower()
            )
            is_student_record_received = (
                model.index(
                    source_row, HEADERS_INDEX["学籍档案是否收到"], source_parent
                )
                .data()
                .lower()
            )
            return not (
                is_student_record_received == "否"
                and is_tuanyuan_archives_received == "否"
            )

        self.filter_model.filterAcceptsRow = filter_accepts_row
        self.filter_model.invalidateFilter()  # 触发重新筛选

    def handle_return_pressed(self):
        if self.return_pressed_flag:
            self.get_only_one_row()
        self.return_pressed_flag = False

    def filter_table_by_id_or_name(self):
        filter_text = self.ui.lineEdit.text()
        source_model = self.filter_model.sourceModel()

        # 空字符串时直接显示所有数据
        if not filter_text:
            self.filter_model.filterAcceptsRow = lambda source_row, source_parent: True
            self.filter_model.invalidateFilter()
            return

        # 创建工作线程
        self.worker = FilterWorker(
            source_model,
            filter_text,
            HEADERS_INDEX["姓名"],
            HEADERS_INDEX["学号"],
        )

        self.worker.moveToThread(self.worker_thread)

        # 连接信号槽
        self.worker_thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.apply_filter_results)
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.handle_return_pressed)
        self.worker.finished.connect(self.worker_thread.quit)

        # 启动线程
        logger.debug("启动后台线程")
        self.worker_thread.start()

    # 添加辅助方法
    def abort_background_filter(self):
        if hasattr(self, "worker") and self.worker.is_running():
            logger.debug("发送终止请求")
            self.worker.interrupt()
            self.worker_thread.quit()
            self.worker_thread.wait()

    @Slot(list)
    def apply_filter_results(self, filtered_rows):
        # 在主线程应用过滤结果
        filtered_set = set(filtered_rows)

        def filter_accepts_row(source_row, source_parent):
            return source_row in filtered_set

        self.filter_model.filterAcceptsRow = filter_accepts_row
        self.filter_model.invalidateFilter()

    def schedule_filter_table_by_id_or_name(self):
        self.line_edit_debounce_timer.stop()
        self.abort_background_filter()
        logger.debug(f"计划过滤表格: {self.ui.lineEdit.text()}")
        self.line_edit_debounce_timer.start(DEBOUNCE_TIME_MS)

    def setup(self):
        # 创建 QStandardItemModel 实例
        model = StudentTableModel()
        self.setWindowIcon(QIcon(ICON))
        # 先设置代理模型
        self.filter_model = DirectSortProxyModel(self)
        self.filter_model.setSourceModel(model)  # 直接使用model而不是tableView.model()
        self.ui.tableView.setModel(self.filter_model)  # 先设置模型

        # 然后设置表头和其他委托
        header = self.ui.tableView.horizontalHeader()
        self.ui.tableView.sortByColumn(
            HEADERS_INDEX["最后更新时间"], Qt.SortOrder.DescendingOrder
        )
        header.setSectionResizeMode(header.ResizeMode.Stretch)
        header.setStretchLastSection(True)

        status_delegate = StatusDelegate(self)
        button_delegate = ButtonDelegate(self)
        button_delegate.buttonClicked.connect(self.handle_selected_button_click)

        self.ui.tableView.setItemDelegateForColumn(
            HEADERS_INDEX["团籍档案是否收到"], status_delegate
        )
        self.ui.tableView.setItemDelegateForColumn(
            HEADERS_INDEX["学籍档案是否收到"], status_delegate
        )
        self.ui.tableView.setItemDelegateForColumn(5, button_delegate)
        self.line_edit_debounce_timer = QTimer(self)
        self.line_edit_debounce_timer.setSingleShot(True)
        self.line_edit_debounce_timer.timeout.connect(self.filter_table_by_id_or_name)
        # 将菜单作为类成员变量，避免被垃圾回收
        self.menu = QMenu(self)
        add_one_row_action = QAction(
            "新增一行", self.menu, icon=QIcon.fromTheme(QIcon.ThemeIcon.ListAdd)
        )
        delete_selected_rows_action = QAction(
            "删除所选行", self.menu, icon=QIcon.fromTheme(QIcon.ThemeIcon.ListRemove)
        )
        print_selected_rows_xueji_action = QAction(
            "打印所选行“学籍档案”",
            self.menu,
            icon=QIcon.fromTheme(QIcon.ThemeIcon.Printer),
        )
        print_selected_rows_tuanyuan_action = QAction(
            "打印所选行“团员材料”",
            self.menu,
            icon=QIcon.fromTheme(QIcon.ThemeIcon.Printer),
        )
        print_selected_rows_dangyuan_action = QAction(
            "打印所选行“党员档案”",
            self.menu,
            icon=QIcon.fromTheme(QIcon.ThemeIcon.Printer),
        )

        add_one_row_action.triggered.connect(self.show_add_one_student_dialog)

        def delete_selected_rows():
            indices = self.ui.tableView.selectedIndexes()
            if not indices:
                QMessageBox.warning(self, "警告", "请先选择要删除的行！")
            else:
                reply = QMessageBox.question(
                    self,
                    "确认删除",
                    "确定删除选中的行吗？",
                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
                )
                if reply == QMessageBox.StandardButton.Yes:
                    self.filter_model.remove_rows(
                        list({index.row() for index in indices})
                    )

        # 新增删除选中行的操作
        delete_selected_rows_action.triggered.connect(delete_selected_rows)

        def print_selected_rows_xueji():
            indices = self.ui.tableView.selectedIndexes()
            if not indices:
                QMessageBox.warning(self, "警告", "请先选择要打印的行！")
            else:
                self.print_student_records(
                    [index.row() for index in indices], "学籍档案"
                )

        def print_selected_rows_tuanyuan():
            indices = self.ui.tableView.selectedIndexes()
            if not indices:
                QMessageBox.warning(self, "警告", "请先选择要打印的行！")
            else:
                self.print_student_records(
                    [index.row() for index in indices], "团员材料"
                )

        def print_selected_rows_dangyuan():
            indices = self.ui.tableView.selectedIndexes()
            if not indices:
                QMessageBox.warning(self, "警告", "请先选择要打印的行！")
            else:
                self.print_student_records(
                    [index.row() for index in indices], "党员档案"
                )

        print_selected_rows_xueji_action.triggered.connect(print_selected_rows_xueji)
        print_selected_rows_tuanyuan_action.triggered.connect(
            print_selected_rows_tuanyuan
        )
        print_selected_rows_dangyuan_action.triggered.connect(
            print_selected_rows_dangyuan
        )
        self.menu.addAction(add_one_row_action)
        self.menu.addAction(delete_selected_rows_action)
        self.menu.addAction(print_selected_rows_xueji_action)
        self.menu.addAction(print_selected_rows_tuanyuan_action)
        self.menu.addAction(print_selected_rows_dangyuan_action)

        # 确保按钮设置正确
        self.ui.toolButton.setPopupMode(QToolButton.InstantPopup)
        self.ui.toolButton.setMenu(self.menu)
        # 最后连接信号
        self.ui.lineEdit.textChanged.connect(self.schedule_filter_table_by_id_or_name)
        self.ui.list_completed_button.clicked.connect(self.filter_table_by_completed)
        self.ui.list_incompleted_button.clicked.connect(
            self.filter_table_by_incompleted
        )
        self.ui.list_all_button.clicked.connect(self.list_all_items)
        self.set_table_rows_num()
        self.filter_model.rowsInserted.connect(self.set_table_rows_num)
        self.filter_model.rowsRemoved.connect(self.set_table_rows_num)
        self.filter_model.modelReset.connect(self.set_table_rows_num)
        self.return_pressed_timer = QTimer(self)
        self.return_pressed_timer.setSingleShot(True)

        def handle_return_pressed_timer():
            if hasattr(self, "worker_thread") and self.worker_thread.isRunning():
                # 标记需要执行后续操作，不阻塞主线程
                self.return_pressed_flag = True
            else:
                self.get_only_one_row()

        self.return_pressed_timer.timeout.connect(handle_return_pressed_timer)
        self.ui.lineEdit.returnPressed.connect(
            lambda: self.return_pressed_timer.start(int(1.1 * DEBOUNCE_TIME_MS))
        )
        self.ui.start_recording_button.clicked.connect(self.handle_start_recording)
        self.ui.stop_recording_button.clicked.connect(self.handle_stop_recording)
        self.ui.actionimport.triggered.connect(self.import_table_data)
        self.ui.actionexport.triggered.connect(self.export_table_data)
        self.ui.actionmergetables.triggered.connect(self.merge_table_data)
        self.ui.actionhelprecordingmode.triggered.connect(self.show_help_recording_mode)
        self.ui.actionhelpprint.triggered.connect(self.show_help_print)
        self.ui.actioncheckbartender.setEnabled(False)
        self.ui.actioncheckbartender.setVisible(False)
        self.ui.actionprintallstuxueji.triggered.connect(
            lambda: self.print_student_records(
                range(self.filter_model.rowCount()), "学籍档案"
            )
        )
        self.ui.actionprintallstutuanyuan.triggered.connect(
            lambda: self.print_student_records(
                range(self.filter_model.rowCount()), "团员材料"
            )
        )
        self.ui.actionprintallstudangyuan.triggered.connect(
            lambda: self.print_student_records(
                range(self.filter_model.rowCount()), "党员档案"
            )
        )
        # self.ui.actioncheckbartender.triggered.connect(self.show_bartender_dialog)
        self.ui.actioncheckprinter.triggered.connect(self.show_printer_dialog)

    def import_table_data(self):
        """Import table data from CSV or Excel file"""
        file_path, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Import Data File",  # Dialog title
            dir="",  # Start directory
            # 新增Excel文件过滤器
            filter="Excel Files (*.xlsx *.xls);;CSV Files (*.csv)",  # File filter
        )
        # Return if user canceled
        if not file_path:
            return

        self.filter_model.import_data(file_path)

    def export_table_data(self):
        """Export table data to CSV or Excel file"""
        # Get save file path with dialog
        file_path, selected_filter = QFileDialog.getSaveFileName(
            parent=self,
            caption="Export Data File",  # Dialog title
            dir="",  # Start directory
            # 新增Excel文件过滤器
            filter="Excel Files (*.xlsx);;CSV Files (*.csv)",  # File filter
        )

        # Return if user canceled
        if not file_path:
            return

        # 根据选择的过滤器设置正确扩展名
        if selected_filter.startswith("Excel Files"):
            if not file_path.lower().endswith(".xlsx"):
                file_path += ".xlsx"
        else:  # CSV Files
            if not file_path.lower().endswith(".csv"):
                file_path += ".csv"

        self.filter_model.export_data(file_path)

    def merge_table_data(self):
        """Merge table data from multiple CSV/Excel files"""  # 更新文档字符串
        # Open file dialog for multi-selection (支持Excel和CSV)
        file_paths, _ = QFileDialog.getOpenFileNames(
            parent=self,
            caption="选中表格文件合并",  # 更新对话框标题
            dir="",  # Start directory
            # 支持Excel和CSV格式选择
            filter="Table Files (*.xlsx *.xls *.csv)",  # 更新文件过滤器
        )

        # Return if user canceled selection
        if not file_paths:
            return  # 移除不必要的列表返回值

        if len(file_paths) < 2:
            QMessageBox.warning(self, "警告", "请选择至少2个文件进行合并")
            return

        # 输出文件对话框 (支持Excel和CSV格式)
        output, selected_filter = QFileDialog.getSaveFileName(
            parent=self,
            caption="Export Merged File",  # 更新对话框标题
            dir="",  # Start directory
            filter="Excel Files (*.xlsx);;CSV Files (*.csv)",  # 更新输出过滤器
        )

        # 处理输出文件扩展名
        if output:
            # 根据选择的过滤器自动补全扩展名
            if selected_filter.startswith(
                "Excel Files"
            ) and not output.lower().endswith(".xlsx"):
                output += ".xlsx"
            elif selected_filter.startswith(
                "CSV Files"
            ) and not output.lower().endswith(".csv"):
                output += ".csv"

        try:
            info_list = merge_table_files(file_paths, output)
            if info_list:
                info_str = "\n".join(info_list)
                QMessageBox.warning(
                    self, "警告", f"合并数据成功，但请注意下面的信息：\n{info_str}"
                )
            else:
                QMessageBox.information(self, "成功", "合并数据成功")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"合并失败: {str(e)}")
            logger.error(f"合并数据失败: {str(e)}")

    def print_student_records(self, indices, receipt_type):
        """Print student records"""
        total = len(indices)
        if total == 0:
            QMessageBox.information(self, "提示", "没有可打印的学生记录")
            return
        reply = QMessageBox.question(
            self,
            "确认打印",
            f"是否确认打印{total}条学生的{receipt_type}？",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if reply == QMessageBox.StandardButton.Yes:
            if not self.check_printer_config():
                return

            # 创建进度条对话框
            progress_dialog = QProgressDialog(
                "正在下发打印任务...", "取消", 0, total, self
            )
            progress_dialog.setWindowTitle("打印进度")
            progress_dialog.setWindowModality(Qt.WindowModal)  # 模态窗口，阻止其他操作
            progress_dialog.show()

            succ_count = 0
            failed_count = 0
            for i, index in enumerate(indices):
                # 检查是否取消操作
                if progress_dialog.wasCanceled():
                    QMessageBox.information(
                        self, "提示", f"已取消打印，已成功下发{succ_count}个任务"
                    )
                    return

                try:
                    student_id = self.filter_model.index(
                        index, HEADERS_INDEX["学号"]
                    ).data()
                    name = self.filter_model.index(index, HEADERS_INDEX["姓名"]).data()
                    receipt_type = receipt_type
                    do_print(
                        name=name,
                        receipt_type=receipt_type,
                        student_id=student_id,
                        paper_width=self.config.printer_config.paper_width_mm,
                        paper_height=self.config.printer_config.paper_height_mm,
                        dpi=self.config.printer_config.dpi,
                        printer_name=self.config.printer_config.name,
                    )
                    succ_count += 1
                except Exception as e:
                    logger.error(
                        f"打印{student_id}-{name}-{receipt_type}时发生错误: {e}"
                    )
                    failed_count += 1

                # 更新进度条
                progress_dialog.setValue(i + 1)
                # 更新进度信息
                progress_dialog.setLabelText(
                    f"正在下发第{i + 1}/{total}个打印任务...\n目前成功{succ_count}个，失败{failed_count}个"
                )

            # 关闭进度条
            progress_dialog.close()

            if failed_count > 0:
                QMessageBox.warning(
                    self,
                    "打印完成",
                    f"成功下发打印{succ_count}个打印任务，失败{failed_count}个，具体原因请查看日志",
                )
            else:
                QMessageBox.information(
                    self, "打印完成", f"成功下发打印{succ_count}个打印任务"
                )


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
